package com.happy3w.bean.json;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.happy3w.bean.factory.BeanConfigItem;
import com.happy3w.bean.factory.BeanConfigItemResolver;
import com.happy3w.bean.factory.DecodeHelper;
import com.happy3w.java.ext.NeedFindIterator;
import com.happy3w.java.ext.NullableOptional;
import com.happy3w.toolkits.iterator.IEasyIterator;
import com.happy3w.toolkits.message.MessageRecorderException;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.lang.reflect.Constructor;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.Stack;

@Slf4j
public class JsonHelper implements DecodeHelper {
    public final ObjectMapper objectMapper = new ObjectMapper();

    public JsonHelper() {
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }

    public String toJson(Object obj) {
        try {
            return objectMapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            throw new MessageRecorderException("Failed to format json.", e);
        }
    }

    public JsonNode readTree(String jsonStr) {
        try {
            return objectMapper.readTree(jsonStr);
        } catch (JsonProcessingException e) {
            throw new MessageRecorderException("Failed to parse json", e);
        }
    }

    public <T> T convertValue(JsonNode node, Class<T> type) {
        try {
            return objectMapper.convertValue(node, type);
        } catch (Exception e) {
            throw new MessageRecorderException("Failed to convert json node to " + type, e);
        }
    }

    public <C extends Collection, T> C collectionFromFile(File file, Class<C> mainType, Class<T> itemType) {
        try {
            CollectionType type = objectMapper.getTypeFactory()
                    .constructCollectionType(mainType, itemType);
            return objectMapper.readValue(file, type);
        } catch (IOException e) {
            throw new MessageRecorderException("Failed to parse json. wrong json: " + file.getPath(), e);
        }
    }

    public <C extends Collection, T> C collectionFromJson(String jsonStr, Class<C> mainType, Class<T> itemType) {
        try {
            CollectionType type = objectMapper.getTypeFactory()
                    .constructCollectionType(mainType, itemType);
            return objectMapper.readValue(jsonStr, type);
        } catch (JsonProcessingException e) {
            throw new MessageRecorderException("Failed to parse json. wrong json: " + jsonStr, e);
        }
    }

    public <T> Set<T> setFromJson(String jsonStr, Class<T> itemType) {
        try {
            CollectionType type = objectMapper.getTypeFactory()
                    .constructCollectionType(Set.class, itemType);
            return objectMapper.readValue(jsonStr, type);
        } catch (JsonProcessingException e) {
            throw new MessageRecorderException("Failed to parse json. wrong json: " + jsonStr, e);
        }
    }

    public <T> List<T> listFromJson(String jsonStr, Class<T> itemType) {
        try {
            CollectionType type = objectMapper.getTypeFactory()
                    .constructCollectionType(List.class, itemType);
            return objectMapper.readValue(jsonStr, type);
        } catch (JsonProcessingException e) {
            throw new MessageRecorderException("Failed to parse json. wrong json: " + jsonStr, e);
        }
    }

    public <T> T fromJson(String jsonStr, Class<T> tClass) {
        try {
            return objectMapper.readValue(jsonStr, tClass);
        } catch (JsonProcessingException e) {
            throw new MessageRecorderException("Failed to parse json. wrong json: " + jsonStr, e);
        }
    }

    @Override
    public <T> T loadFromJson(String json, Class<T> tClass) {
        try {
            return objectMapper.readValue(json, tClass);
        } catch (IOException e) {
            throw new MessageRecorderException("Failed to parse json", e);
        }
    }

    @Override
    public <T> T loadFromFile(File file, Class<T> tClass) {
        try {
            return objectMapper.readValue(file, tClass);
        } catch (IOException e) {
            throw new MessageRecorderException("Failed to parse json", e);
        }
    }

    public void saveToFile(File file, Object obj) {
        try {
            objectMapper.writeValue(file, obj);
        } catch (IOException e) {
            throw new MessageRecorderException("Failed to save json", e);
        }
    }

    @Override
    public void registerSubTypes(Class... subTypes) {
        for (Class subType : subTypes) {
            registerSubType(subType);
        }
    }

    public void registerSubType(Class subType) {
        ensureHasPublicConstructor(subType);
        if (BeanConfigItem.class.isAssignableFrom(subType)) {
            BeanConfigItemResolver.registerSubType(subType);
        } else {
            ensureHasAnnotationJsonTypeName(subType);
            objectMapper.registerSubtypes(subType);
        }
        log.info("register sub type:{}", subType);
    }


    private void ensureHasAnnotationJsonTypeName(Class<?> clazz) {
        if (!clazz.isAnnotationPresent(com.fasterxml.jackson.annotation.JsonTypeName.class)) {
            throw new MessageRecorderException(MessageFormat.format("{0} must have @JsonTypeName annotation.", clazz));
        }
    }

    private Constructor<?> ensureHasPublicConstructor(Class<?> clazz) {
        try {
            return clazz.getConstructor();
        } catch (NoSuchMethodException e) {
            throw new MessageRecorderException(MessageFormat.format("{0} must have public constructor.", clazz));
        }
    }

    public IEasyIterator<String> splitJson(String jsonStr) {
        InputStream inputStream = new ByteArrayInputStream(jsonStr.getBytes(StandardCharsets.UTF_8));
        return new JsonSplitter(inputStream);
    }

    public IEasyIterator<String> splitJson(InputStream inputStream) {
        return new JsonSplitter(inputStream);
    }

    private static class JsonSplitter extends NeedFindIterator<String> implements IEasyIterator<String> {
        private final InputStream inputStream;
        private final InputStreamReader reader;
        private StringBuilder buf = new StringBuilder();
        private Stack<Integer> bracketStack = new Stack<>();

        private JsonSplitter(InputStream inputStream) {
            this.inputStream = inputStream;
            this.reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
        }

        @Override
        protected NullableOptional<String> findNext() {
            StringDecoder decoder = new StringDecoder();
            try {
                while (true) {
                    int value = reader.read();
                    if (value == -1) {
                        break;
                    }
                    char ch = (char) value;
                    buf.append(ch);
                    decoder.accept(ch);
                    if (decoder.inString()) {
                        continue;
                    }

                    if (value == '{' || value == '[') {
                        bracketStack.push(value);
                    } else if (value == '}' || value == ']') {
                        if (bracketStack.isEmpty()) {
                            throw new MessageRecorderException("Invalid json format.");
                        }
                        if (value == '}' && bracketStack.peek() != '{') {
                            throw new MessageRecorderException("Invalid json format.");
                        }
                        if (value == ']' && bracketStack.peek() != '[') {
                            throw new MessageRecorderException("Invalid json format.");
                        }
                        bracketStack.pop();
                        if (bracketStack.isEmpty()) {
                            String text = buf.toString();
                            buf.setLength(0);
                            return NullableOptional.of(text);
                        }
                    }
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            return NullableOptional.empty();
        }

        @Override
        public void close() throws Exception {
            inputStream.close();
        }
    }

}
